#include <stdio.h>
#include <stdint.h>
#include "ibm.h"
#include "disc.h"
#include "disc_fdi.h"
#include "fdc.h"
#include "fdi2raw.h"

static struct {
        FILE *f;
        FDI *h;
        uint8_t track_data[2][4][256 * 1024];

        int sides;
        int tracklen[2][4];
        int trackindex[2][4];

        int lasttrack;
} fdi[2];

static uint8_t fdi_timing[256 * 1024];
static int fdi_pos;
static int fdi_revs;

static int fdi_sector, fdi_track, fdi_side, fdi_drive, fdi_density, fdi_n;
static int fdi_inread, fdi_inwrite, fdi_readpos, fdi_inreadaddr;

static uint16_t CRCTable[256];

static int pollbytesleft = 0, pollbitsleft = 0;

static void fdi_setupcrc(uint16_t poly, uint16_t rvalue) {
        int c = 256, bc;
        uint16_t crctemp;

        while (c--) {
                crctemp = c << 8;
                bc = 8;

                while (bc--) {
                        if (crctemp & 0x8000) {
                                crctemp = (crctemp << 1) ^ poly;
                        } else {
                                crctemp <<= 1;
                        }
                }

                CRCTable[c] = crctemp;
        }
}

void fdi_init() {
        //        printf("FDI reset\n");
        memset(&fdi, 0, sizeof(fdi));
        fdi_setupcrc(0x1021, 0xcdb4);
}

int fdi_hole(int drive) {
        switch (fdi2raw_get_bit_rate(fdi[drive].h)) {
        case 1000:
                return 2;
        case 500:
                return 1;
        default:
                return 0;
        }
}

void fdi_load(int drive, char *fn) {
        writeprot[drive] = fwriteprot[drive] = 1;
        fdi[drive].f = fopen(fn, "rb");
        if (!fdi[drive].f)
                return;
        fdi[drive].h = fdi2raw_header(fdi[drive].f);
        //        if (!fdih[drive]) printf("Failed to load!\n");
        fdi[drive].lasttrack = fdi2raw_get_last_track(fdi[drive].h);
        fdi[drive].sides = fdi2raw_get_last_head(fdi[drive].h) + 1;
        //        printf("Last track %i\n",fdilasttrack[drive]);
        drives[drive].seek = fdi_seek;
        drives[drive].readsector = fdi_readsector;
        drives[drive].writesector = fdi_writesector;
        drives[drive].readaddress = fdi_readaddress;
        drives[drive].hole = fdi_hole;
        drives[drive].poll = fdi_poll;
        drives[drive].format = fdi_format;
        drives[drive].stop = fdi_stop;
        //        pclog("Loaded as FDI\n");
}

void fdi_close(int drive) {
        if (fdi[drive].h)
                fdi2raw_header_free(fdi[drive].h);
        if (fdi[drive].f)
                fclose(fdi[drive].f);
        fdi[drive].f = NULL;
}

void fdi_seek(int drive, int track) {
        int density;

        if (!fdi[drive].f)
                return;
        //        printf("Track start %i\n",track);
        if (track < 0)
                track = 0;
        if (track > fdi[drive].lasttrack)
                track = fdi[drive].lasttrack - 1;

        for (density = 0; density < 4; density++) {
                int c = fdi2raw_loadtrack(fdi[drive].h, (uint16_t *)fdi[drive].track_data[0][density], (uint16_t *)fdi_timing,
                                          track * fdi[drive].sides, &fdi[drive].tracklen[0][density],
                                          &fdi[drive].trackindex[0][density], NULL, density);
                if (!c)
                        memset(fdi[drive].track_data[0][density], 0, fdi[drive].tracklen[0][density]);

                if (fdi[drive].sides == 2) {
                        c = fdi2raw_loadtrack(fdi[drive].h, (uint16_t *)fdi[drive].track_data[1][density], (uint16_t *)fdi_timing,
                                              (track * fdi[drive].sides) + 1, &fdi[drive].tracklen[1][density],
                                              &fdi[drive].trackindex[1][density], NULL, density);
                        if (!c)
                                memset(fdi[drive].track_data[1][density], 0, fdi[drive].tracklen[1][density]);
                } else {
                        memset(fdi[drive].track_data[1][density], 0, 65536);
                        fdi[drive].tracklen[1][density] = fdi[drive].trackindex[1][density] = 10000;
                }
        }
}

void fdi_writeback(int drive, int track) { return; }

void fdi_readsector(int drive, int sector, int track, int side, int rate, int sector_size) {
        fdi_revs = 0;
        fdi_sector = sector;
        fdi_track = track;
        fdi_side = side;
        fdi_n = sector_size;
        fdi_drive = drive;
        if (rate == 2)
                fdi_density = 1;
        if (rate == 0)
                fdi_density = 2;
        if (rate == 3)
                fdi_density = 3;

        //        pclog("FDI Read sector %i %i %i %i %i\n",drive,side,track,sector, fdi_density);
        //        if (pollbytesleft)
        //                pclog("In the middle of a sector!\n");

        fdi_inread = 1;
        fdi_inwrite = 0;
        fdi_inreadaddr = 0;
        fdi_readpos = 0;
}

void fdi_writesector(int drive, int sector, int track, int side, int rate, int sector_size) {
        fdi_revs = 0;
        fdi_sector = sector;
        fdi_track = track;
        fdi_side = side;
        fdi_n = sector_size;
        fdi_drive = drive;
        if (rate == 2)
                fdi_density = 1;
        if (rate == 0)
                fdi_density = 2;
        if (rate == 3)
                fdi_density = 3;
        //        pclog("Write sector %i %i %i %i\n",drive,side,track,sector);

        fdi_inread = 0;
        fdi_inwrite = 1;
        fdi_inreadaddr = 0;
        fdi_readpos = 0;
}

void fdi_readaddress(int drive, int track, int side, int rate) {
        fdi_revs = 0;
        fdi_track = track;
        fdi_side = side;
        fdi_drive = drive;
        if (rate == 2)
                fdi_density = 1;
        if (rate == 0)
                fdi_density = 2;
        if (rate == 3)
                fdi_density = 3;
        //        pclog("Read address %i %i %i %i %i %p\n",drive,side,track, rate, fdi_density, &fdi_inreadaddr);

        fdi_inread = 0;
        fdi_inwrite = 0;
        fdi_inreadaddr = 1;
        fdi_readpos = 0;
}

void fdi_format(int drive, int track, int side, int rate, uint8_t fill) {
        fdi_revs = 0;
        fdi_track = track;
        fdi_side = side;
        fdi_drive = drive;
        if (rate == 2)
                fdi_density = 1;
        if (rate == 0)
                fdi_density = 2;
        if (rate == 3)
                fdi_density = 3;
        //        pclog("Format %i %i %i\n",drive,side,track);

        fdi_inread = 0;
        fdi_inwrite = 1;
        fdi_inreadaddr = 0;
        fdi_readpos = 0;
}

static uint16_t fdi_buffer;
static int readidpoll = 0, readdatapoll = 0, fdi_nextsector = 0, inreadop = 0;
static uint8_t fdi_sectordat[1026];
static int lastfdidat[2], sectorcrc[2];
static int sectorsize, fdc_sectorsize;
static int ddidbitsleft = 0;

static uint8_t decodefm(uint16_t dat) {
        uint8_t temp;
        temp = 0;
        if (dat & 0x0001)
                temp |= 1;
        if (dat & 0x0004)
                temp |= 2;
        if (dat & 0x0010)
                temp |= 4;
        if (dat & 0x0040)
                temp |= 8;
        if (dat & 0x0100)
                temp |= 16;
        if (dat & 0x0400)
                temp |= 32;
        if (dat & 0x1000)
                temp |= 64;
        if (dat & 0x4000)
                temp |= 128;
        return temp;
}

void fdi_stop() {
        //        pclog("fdi_stop\n");
        fdi_inread = fdi_inwrite = fdi_inreadaddr = 0;
        fdi_nextsector = ddidbitsleft = pollbitsleft = 0;
}

static uint16_t crc;

static void calccrc(uint8_t byte) { crc = (crc << 8) ^ CRCTable[(crc >> 8) ^ byte]; }

static int fdi_indextime_blank = 6250 * 8;
void fdi_poll() {
        int tempi, c;
        int bitcount;

        for (bitcount = 0; bitcount < 16; bitcount++) {
                if (fdi_pos >= fdi[fdi_drive].tracklen[fdi_side][fdi_density]) {
                        fdi_pos = 0;
                        if (fdi[fdi_drive].tracklen[fdi_side][fdi_density])
                                fdc_indexpulse();
                        else {
                                fdi_indextime_blank--;
                                if (!fdi_indextime_blank) {
                                        fdi_indextime_blank = 6250 * 8;
                                        fdc_indexpulse();
                                }
                        }
                }
                tempi = fdi[fdi_drive].track_data[fdi_side][fdi_density][((fdi_pos >> 3) & 0xFFFF) ^ 1] &
                        (1 << (7 - (fdi_pos & 7)));
                fdi_pos++;
                fdi_buffer <<= 1;
                fdi_buffer |= (tempi ? 1 : 0);
                if (fdi_inwrite) {
                        fdi_inwrite = 0;
                        fdc_writeprotect();
                        return;
                }
                if (!fdi_inread && !fdi_inreadaddr)
                        return;
                if (fdi_pos == fdi[fdi_drive].trackindex[fdi_side][fdi_density]) {
                        fdi_revs++;
                        if (fdi_revs == 3) {
                                //                        pclog("Not found!\n");
                                fdc_notfound(FDC_STATUS_NOT_FOUND);
                                fdi_inread = fdi_inreadaddr = 0;
                                return;
                        }
                        if (fdi_sector == SECTOR_FIRST)
                                fdi_sector = SECTOR_NEXT;
                }
                if (pollbitsleft) {
                        pollbitsleft--;
                        if (!pollbitsleft) {
                                pollbytesleft--;
                                if (pollbytesleft)
                                        pollbitsleft = 16; /*Set up another word if we need it*/
                                if (readidpoll) {
                                        fdi_sectordat[5 - pollbytesleft] = decodefm(fdi_buffer);
                                        if (!pollbytesleft) {
                                                //                                        pclog("Header over %i,%i %i,%i\n",
                                                //                                        fdi_sectordat[0], fdi_sectordat[2],
                                                //                                        fdi_track, fdi_sector);
                                                if ((fdi_sectordat[0] == fdi_track && (fdi_sectordat[3] == fdi_n) &&
                                                     (fdi_sectordat[2] == fdi_sector || fdi_sector == SECTOR_NEXT)) ||
                                                    fdi_inreadaddr) {
                                                        crc = (fdi_density) ? 0xcdb4 : 0xffff;
                                                        calccrc(0xFE);
                                                        for (c = 0; c < 4; c++)
                                                                calccrc(fdi_sectordat[c]);

                                                        if ((crc >> 8) != fdi_sectordat[4] || (crc & 0xFF) != fdi_sectordat[5]) {
                                                                //                                                        pclog("Header
                                                                //                                                        CRC
                                                                //                                                        error :
                                                                //                                                        %02X
                                                                //                                                        %02X
                                                                //                                                        %02X
                                                                //                                                        %02X\n",crc>>8,crc&0xFF,fdi_sectordat[4],fdi_sectordat[5]);
                                                                //                                                        dumpregs();
                                                                //                                                        exit(-1);
                                                                inreadop = 0;
                                                                if (fdi_inreadaddr)
                                                                        fdc_sectorid(fdi_sectordat[0], fdi_sectordat[1],
                                                                                     fdi_sectordat[2], fdi_sectordat[3],
                                                                                     fdi_sectordat[4], fdi_sectordat[5]);
                                                                else
                                                                        fdc_headercrcerror();
                                                                return;
                                                        }
                                                        //                                                pclog("Sector %i,%i
                                                        //                                                %i,%i\n",
                                                        //                                                fdi_sectordat[0],
                                                        //                                                fdi_sectordat[2],
                                                        //                                                fdi_track, fdi_sector);
                                                        if (fdi_sectordat[0] == fdi_track &&
                                                            (fdi_sectordat[2] == fdi_sector || fdi_sector == SECTOR_NEXT) &&
                                                            fdi_inread && !fdi_inreadaddr) {
                                                                fdi_nextsector = 1;
                                                                readidpoll = 0;
                                                                sectorsize = (1 << (fdi_sectordat[3] + 7)) + 2;
                                                                fdc_sectorsize = fdi_sectordat[3];
                                                        }
                                                        if (fdi_inreadaddr) {
                                                                fdc_sectorid(fdi_sectordat[0], fdi_sectordat[1], fdi_sectordat[2],
                                                                             fdi_sectordat[3], fdi_sectordat[4],
                                                                             fdi_sectordat[5]);
                                                                fdi_inreadaddr = 0;
                                                        }
                                                }
                                        }
                                }
                                if (readdatapoll) {
                                        //                                pclog("readdatapoll %i %02x\n", pollbytesleft,
                                        //                                decodefm(fdi_buffer));
                                        if (pollbytesleft > 1) {
                                                calccrc(decodefm(fdi_buffer));
                                        } else
                                                sectorcrc[1 - pollbytesleft] = decodefm(fdi_buffer);
                                        if (!pollbytesleft) {
                                                fdi_inread = 0;
                                                //#if 0
                                                if ((crc >> 8) != sectorcrc[0] ||
                                                    (crc & 0xFF) !=
                                                            sectorcrc[1]) // || (fditrack==79 && fdisect==4 && fdc_side&1))
                                                {
                                                        //                                                pclog("Data CRC error :
                                                        //                                                %02X %02X %02X %02X %i
                                                        //                                                %04X
                                                        //                                                %02X%02X\n",crc>>8,crc&0xFF,sectorcrc[0],sectorcrc[1],fdi_pos,crc,sectorcrc[0],sectorcrc[1]);
                                                        inreadop = 0;
                                                        fdc_data(decodefm(lastfdidat[1]));
                                                        fdc_finishread();
                                                        fdc_datacrcerror();
                                                        readdatapoll = 0;
                                                        return;
                                                }
                                                //#endif
                                                //                                        pclog("End of FDI read %02X %02X %02X
                                                //                                        %02X\n",crc>>8,crc&0xFF,sectorcrc[0],sectorcrc[1]);
                                                fdc_data(decodefm(lastfdidat[1]));
                                                fdc_finishread();
                                        } else if (lastfdidat[1] != 0)
                                                fdc_data(decodefm(lastfdidat[1]));
                                        lastfdidat[1] = lastfdidat[0];
                                        lastfdidat[0] = fdi_buffer;
                                        if (!pollbytesleft)
                                                readdatapoll = 0;
                                }
                        }
                }
                if (fdi_buffer == 0x4489 && fdi_density) {
                        //                rpclog("Found sync\n");
                        ddidbitsleft = 17;
                }

                if (fdi_buffer == 0xF57E && !fdi_density) {
                        pollbytesleft = 6;
                        pollbitsleft = 16;
                        readidpoll = 1;
                }
                if ((fdi_buffer == 0xF56F || fdi_buffer == 0xF56A) && !fdi_density) {
                        if (fdi_nextsector) {
                                pollbytesleft = sectorsize;
                                pollbitsleft = 16;
                                readdatapoll = 1;
                                fdi_nextsector = 0;
                                crc = 0xffff;
                                if (fdi_buffer == 0xF56A)
                                        calccrc(0xF8);
                                else
                                        calccrc(0xFB);
                                lastfdidat[0] = lastfdidat[1] = 0;
                        }
                }
                if (ddidbitsleft) {
                        ddidbitsleft--;
                        if (!ddidbitsleft && !readdatapoll) {
                                //                        printf("ID bits over %04X %02X
                                //                        %i\n",fdibuffer,decodefm(fdibuffer),fdipos);
                                if (decodefm(fdi_buffer) == 0xFE) {
                                        //                                printf("Sector header %i %i\n", fdi_inread,
                                        //                                fdi_inreadaddr);
                                        pollbytesleft = 6;
                                        pollbitsleft = 16;
                                        readidpoll = 1;
                                } else if (decodefm(fdi_buffer) == 0xFB) {
                                        //                                printf("Data header %i %i\n", fdi_inread,
                                        //                                fdi_inreadaddr);
                                        if (fdi_nextsector) {
                                                pollbytesleft = sectorsize;
                                                pollbitsleft = 16;
                                                readdatapoll = 1;
                                                fdi_nextsector = 0;
                                                crc = 0xcdb4;
                                                if (fdi_buffer == 0xF56A)
                                                        calccrc(0xF8);
                                                else
                                                        calccrc(0xFB);
                                                lastfdidat[0] = lastfdidat[1] = 0;
                                        }
                                }
                        }
                }
        }
}
